﻿using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.WowAddonStudio.FrameXml.Components.Events;
using Microsoft.WowAddonStudio.FrameXml.Components.Support;
using Microsoft.WowAddonStudio.FrameXml.Serialization;

namespace Microsoft.WowAddonStudio.FrameXml.Components.Descriptors
{
    /// <summary>
    /// Provides a descriptor of a property on a class.
    /// </summary>
    public class FrameXmlEventPropertyDescriptor : PropertyDescriptor
    {
        private readonly Regex methodRegex = new Regex(@"^\w*$");
        
        #region Delegates

        public delegate string GetScript();

        #endregion

        private readonly FrameXmlEventDescriptor eventDescriptor;
        private readonly Type type;

        /// <summary>
        /// Initializes a new instance of the <see cref="FrameXmlPropertyDescriptor"/> class.
        /// </summary>
        /// <param name="eventDescriptor">The descr.</param>
        public FrameXmlEventPropertyDescriptor(FrameXmlEventDescriptor eventDescriptor)
            : this(eventDescriptor, null)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="FrameXmlPropertyDescriptor"/> class.
        /// </summary>
        /// <param name="eventDescriptor">A <see cref="T:System.ComponentModel.MemberDescriptor"/> containing the name of the member and its attributes.</param>
        /// <param name="attrs">An <see cref="T:System.Attribute"/> array containing the attributes you want to associate with the property.</param>
        public FrameXmlEventPropertyDescriptor(FrameXmlEventDescriptor eventDescriptor, Attribute[] attrs)
            : base(eventDescriptor, attrs)
        {
            this.eventDescriptor = eventDescriptor;

            type = eventDescriptor.ComponentType;
        }

        /// <summary>
        /// Gets or sets the script block.
        /// </summary>
        /// <value>The script block.</value>
        public string ScriptBlock { get; set; }

        /// <summary>
        /// Gets the event descriptor.
        /// </summary>
        public FrameXmlEventDescriptor Event
        {
            get { return eventDescriptor; }
        }

        /// <summary>
        /// Gets the frame XML event.
        /// </summary>
        /// <value>The frame XML event.</value>
        protected EventName EventName
        {
            get { return eventDescriptor.EventName; }
        }

        /// <summary>
        /// Gets the type converter for this property.
        /// </summary>
        /// <value></value>
        /// <returns>A <see cref="T:System.ComponentModel.TypeConverter"/> that is used to convert the <see cref="T:System.Type"/> of this property.</returns>
        /// <PermissionSet>
        /// 	<IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode"/>
        /// </PermissionSet>
        public override TypeConverter Converter
        {
            get { return new FrameXmlEventTypeConverter(eventDescriptor); }
        }

        /// <summary>
        /// When overridden in a derived class, gets the type of the component this property is bound to.
        /// </summary>
        /// <value></value>
        /// <returns>A <see cref="T:System.Type"/> that represents the type of component this property is bound to. When the <see cref="M:System.ComponentModel.PropertyDescriptor.GetValue(System.Object)"/> or <see cref="M:System.ComponentModel.PropertyDescriptor.SetValue(System.Object,System.Object)"/> methods are invoked, the object specified might be an instance of this type.</returns>
        public override Type ComponentType
        {
            get { return typeof (LayoutFrameControl); }
        }

        /// <summary>
        /// When overridden in a derived class, gets a value indicating whether this property is read-only.
        /// </summary>
        /// <value></value>
        /// <returns>true if the property is read-only; otherwise, false.</returns>
        public override bool IsReadOnly
        {
            get { return false; }
        }

        /// <summary>
        /// When overridden in a derived class, gets the type of the property.
        /// </summary>
        /// <value></value>
        /// <returns>A <see cref="T:System.Type"/> that represents the type of the property.</returns>
        public override Type PropertyType
        {
            get { return typeof(FrameXmlEventHandler); }
        }

        /// <summary>
        /// When overridden in a derived class, returns whether resetting an object changes its value.
        /// </summary>
        /// <param name="component">The component to test for reset capability.</param>
        /// <returns>
        /// true if resetting the component changes its value; otherwise, false.
        /// </returns>
        public override bool CanResetValue(object component)
        {
            return (GetValue(component) != null);
        }

        /// <summary>
        /// When overridden in a derived class, gets the current value of the property on a component.
        /// </summary>
        /// <param name="component">The component with the property for which to retrieve the value.</param>
        /// <returns>
        /// The value of a property for a given component.
        /// </returns>
        public override object GetValue(object component)
        {
            if (component == null)
                return null;

            object target = GetInvocationTarget(type, component);

            if (target != null && target is FrameControl)
            {
                var frameControl = (FrameControl)target;

                if (frameControl.ScriptContainer != null && frameControl.ScriptContainer.Contains(EventName))
                {
                    string scriptBlock = frameControl.ScriptContainer[EventName].ScriptBlock;

                    return !string.IsNullOrEmpty(scriptBlock) ? scriptBlock.Trim() : scriptBlock;
                }
            }
            return null;
        }

        /// <summary>
        /// When overridden in a derived class, resets the value for this property of the component to the default value.
        /// </summary>
        /// <param name="component">The component with the property value that is to be reset to the default value.</param>
        public override void ResetValue(object component)
        {
            SetValue(component, null);
        }

        /// <summary>
        /// When overridden in a derived class, sets the value of the component to a different value.
        /// </summary>
        /// <param name="component">The component with the property value that is to be set.</param>
        /// <param name="value">The new value.</param>
        public override void SetValue(object component, object value)
        {
            if (Attributes != null)
            {
                ReadOnlyAttribute readOnlyAttribute = Attributes.OfType<ReadOnlyAttribute>().FirstOrDefault();
                if (readOnlyAttribute != null && readOnlyAttribute.IsReadOnly) return;
            }

            //Get the property owner component
            object target = GetInvocationTarget(type, component);

            if (target != null)
            {
                if (target is FrameControl)
                {
                    var frameControl = (FrameControl)target;

                    //Get IComponentChangeService implementation from Site of control
                    var componentChangeService = (IComponentChangeService) frameControl.Site.GetService(typeof (IComponentChangeService));
                    //Notify listeners about component changing
                    if (componentChangeService != null)
                        componentChangeService.OnComponentChanging(frameControl, this);

                    //Add or remove event handler
                    if (value != null)
                    {
                        string scriptValue = value.ToString();

                        if (methodRegex.Match(scriptValue).Success)
                            ScriptBlock = String.Format("{0}();", scriptValue);
                        else
                            ScriptBlock = scriptValue;

                        if (!String.IsNullOrEmpty(ScriptBlock))
                        {
                            //Inject script to control's ScriptContainer
                            ScriptInjector.InjectScript(frameControl, EventName, ScriptBlock);

                            //Add event handler into frame xml or lua code file
                            eventDescriptor.AddEventHandler(component, new GetScript(() => ScriptBlock));
                        }
                    }
                    else
                    {
                        //Remove event handler from frame xml or lua code file
                        ScriptInjector.RemoveScript(frameControl, EventName);
                        eventDescriptor.RemoveEventHandler(component, new GetScript(() => ScriptBlock));
                    }

                    //Notify listeners about event handler script changes
                    if (componentChangeService != null)
                        componentChangeService.OnComponentChanged(component, this, GetValue(component), ScriptBlock);

                    //Notify listeners about ValueChanged
                    OnValueChanged(component, EventArgs.Empty);
                }
            }
        }


        /// <summary>
        /// When overridden in a derived class, determines a value indicating whether the value of this property needs to be persisted.
        /// </summary>
        /// <param name="component">The component with the property to be examined for persistence.</param>
        /// <returns>
        /// true if the property should be persisted; otherwise, false.
        /// </returns>
        public override bool ShouldSerializeValue(object component)
        {
            return CanResetValue(component);
        }


        /// <summary>
        /// This method returns the object that should be used during invocation of members.
        /// </summary>
        /// <param name="type">The <see cref="T:System.Type"/> of the invocation target.</param>
        /// <param name="instance">The potential invocation target.</param>
        /// <returns>
        /// The <see cref="T:System.Object"/> that should be used during invocation of members.
        /// </returns>
        protected override object GetInvocationTarget(Type type, object instance)
        {
            object invocationTarget = base.GetInvocationTarget(type, instance);

            var descriptor = invocationTarget as ICustomTypeDescriptor;

            if (descriptor != null)
                invocationTarget = descriptor.GetPropertyOwner(this);

            return invocationTarget;
        }
    }
}